package demo1

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/prometheus/common/log"
)

// json.Marshal  json.Unmarshal

type Person struct {
	Name   string `json:"name"`
	Age    int64
	Weight float64 `json:"-"` // 忽略字段 序列化和反序列化
}

type User struct {
	Name  string   `json:"name,omitempty"`
	Email string   `json:"email,omitempty"`
	Hobby []string `json:"hobby,omitempty"`
}

func NewUser() {
	u1 := User{
		Name: "千米",
	}
	// struct ->json
	b, _ := json.Marshal(u1)
	fmt.Println(string(b))
}

type Users struct {
	Name    string   `json:"name"`
	Email   string   `json:"email,omitempty"`
	Hobby   []string `json:"hobby,omitempty"`
	Profile          // json:"profile" 就变成嵌套的
}

type Profile struct {
	Website string `json:"site"`
	Slogan  string `json:"slogan"`
}

// 匿名嵌套Profile时序列化后的json串为单层的：
// 想要变成嵌套的json串，需要改为具名嵌套或定义字段tag：

func nestedStructDemo() {
	u1 := Users{
		Name:  "七米",
		Hobby: []string{"足球", "双色球"},
	}
	b, err := json.Marshal(u1)
	if err != nil {
		fmt.Printf("json.Marshal failed, err:%v\n", err)
		return
	}
	fmt.Printf("str:%s\n", b)
}

// -----------------------------------------------------------

// 结构体 tag 介绍使用
// 命名规范:`key1:"value1" key2:"value2"`
// 结构体tag由一个或多个键值对组成。键与值使用冒号分隔，值用双引号括起来。
// 同一个结构体字段可以设置多个键值对tag，不同的键值对之间使用空格分隔。
// json:"omitempty" 是忽略一些没有值的数据  序列化/反序列化时候
func NewPerson() {
	p1 := Person{
		Name:   "气密",
		Age:    10,
		Weight: 72.1,
	}
	// struct -->string
	str1, err := json.Marshal(p1)
	if err != nil {
		fmt.Println("json.Marshal failed")
		return
	}
	fmt.Println(string(str1))

	var p2 Person
	err = json.Unmarshal(str1, &p2)
	if err != nil {
		fmt.Println("json.Unmarshal failed")
		return
	}
	fmt.Println(p2)
}

// 我们需要json序列化User，但是不想把密码也序列化，
// 又不想修改User结构体，
// 这个时候我们就可以使用创建另外一个结构体PublicUser匿名嵌套原User，
// 同时指定Password字段为匿名结构体指针类型，并添加omitemptytag，示例代码如下：

// -----------------------------------------------------------
type User1 struct {
	Name     string `json:"name"`
	Password string `json:"password"`
}

type PublicUser struct {
	*User1             // 匿名嵌套
	Password *struct{} `json:"password,omitempty"`
}

// 使用该方法可以屏蔽自定义的序列和反序列化的字段
func omitPasswordDemo() {
	u1 := User1{
		Name:     "七米",
		Password: "123456",
	}
	b, err := json.Marshal(PublicUser{User1: &u1})
	if err != nil {
		fmt.Printf("json.Marshal u1 failed, err:%v\n", err)
		return
	}
	fmt.Printf("str:%s\n", b) // str:{"name":"七米"}

	var u2 PublicUser
	json.Unmarshal(b, &u2)
	fmt.Println(u2.User1)
}

// -----------------------------------------------------------
// 优雅处理字符串格式的数字
// 需求：
// 有时候，前端在传递来的json数据中可能会使用字符串类型的数字，
// 这个时候可以在结构体tag中添加string来告诉json包从字符串中解析相应字段的数据：
type Card struct {
	ID    int64   `json:"id,string"`
	Score float64 `json:"score,string"`
}

func IntCard() {
	jsonStr1 := `{"id":"123","score":"13.5"}`
	var card Card
	json.Unmarshal([]byte(jsonStr1), &card)
	fmt.Printf("%#v\n", card)
}

// -----------------------------------------------------------
// 在 JSON 协议中是没有整型和浮点型之分的，它们统称为number。
// json字符串中的数字经过Go语言中的json包反序列化之后都会成为float64类型。
// 下面的代码便演示了这个问题：
func jsonDemo() {
	// map[string]interface{} -> json string
	var m = make(map[string]interface{}, 1)
	m["count"] = 1 // int
	b, err := json.Marshal(m)
	if err != nil {
		fmt.Printf("marshal failed, err:%v\n", err)
	}
	fmt.Printf("str:%#v\n", string(b))
	// json string -> map[string]interface{}
	var m2 map[string]interface{}
	err = json.Unmarshal(b, &m2)
	if err != nil {
		fmt.Printf("unmarshal failed, err:%v\n", err)
		return
	}
	fmt.Printf("value:%v\n", m2["count"]) // 1
	fmt.Printf("type:%T\n", m2["count"])  // float64
}

// -----------------------------------------------------------
// 这种场景下如果想更合理的处理数字就需要使用decoder去反序列化，示例代码如下：
// -----------------------------------------------------------

// 自定义解析时间字段
type Post struct {
	CreateTime time.Time `json:"create_time"`
}

func timeFieldDemo() {
	p1 := Post{CreateTime: time.Now()}
	b, _ := json.Marshal(p1)

	var p2 Post
	json.Unmarshal(b, &p2)
	fmt.Println(p2)

	var p3 Post
	jsonStr1 := `{"create_time":"2020-08-25 15:08:45"}`
	err := json.Unmarshal([]byte(jsonStr1), &p3)
	if err != nil {
		log.Error("json.Unmarshal failed...")
	}
	fmt.Println(p3)
}

// -----------------------------------------------------------
// 自定义MarshalJSON和UnmarshalJSON方法
// 上面那种自定义类型的方法稍显啰嗦了一点，下面来看一种相对便捷的方法。
// 首先你需要知道的是，如果你能够为某个类型实现了MarshalJSON()([]byte, error)
// 和UnmarshalJSON(b []byte) error方法，那么这个类型在序列化（MarshalJSON）/反序列化
// （UnmarshalJSON）时就会使用你定制的相应方法。
type Order struct {
	ID          int       `json:"id"`
	Title       string    `json:"title"`
	CreatedTime time.Time `json:"created_time"`
}

const layout = "2006-01-02 15:04:05"

// MarshalJSON 为Order类型实现自定义的MarshalJSON方法
func (o *Order) MarshalJSON() ([]byte, error) {
	type TempOrder Order // 定义与Order字段一致的新类型
	return json.Marshal(struct {
		CreatedTime string `json:"created_time"`
		*TempOrder         // 避免直接嵌套Order进入死循环
	}{
		CreatedTime: o.CreatedTime.Format(layout),
		TempOrder:   (*TempOrder)(o),
	})
}

// UnmarshalJSON 为Order类型实现自定义的UnmarshalJSON方法
func (o *Order) UnmarshalJSON(data []byte) error {
	type TempOrder Order // 定义与Order字段一致的新类型
	ot := struct {
		CreatedTime string `json:"created_time"`
		*TempOrder         // 避免直接嵌套Order进入死循环
	}{
		TempOrder: (*TempOrder)(o),
	}
	if err := json.Unmarshal(data, &ot); err != nil {
		return err
	}
	var err error
	o.CreatedTime, err = time.Parse(layout, ot.CreatedTime)
	if err != nil {
		return err
	}
	return nil
}

// 自定义序列化方法
func customMethodDemo() {
	o1 := Order{
		ID:          123456,
		Title:       "《七米的Go学习笔记》",
		CreatedTime: time.Now(),
	}
	// 通过自定义的MarshalJSON方法实现struct -> json string
	b, err := json.Marshal(&o1)
	if err != nil {
		fmt.Printf("json.Marshal o1 failed, err:%v\n", err)
		return
	}
	fmt.Printf("str:%s\n", b)
	// 通过自定义的UnmarshalJSON方法实现json string -> struct
	jsonStr := `{"created_time":"2020-04-05 10:18:20","id":123456,"title":"《七米的Go学习笔记》"}`
	var o2 Order
	if err := json.Unmarshal([]byte(jsonStr), &o2); err != nil {
		fmt.Printf("json.Unmarshal failed, err:%v\n", err)
		return
	}
	fmt.Printf("o2:%#v\n", o2)
}

// -----------------------------------------------------------
// 使用匿名结构体添加字段
// 使用内嵌结构体能够扩展结构体的字段，
// 但有时候我们没有必要单独定义新的结构体，可以使用匿名结构体简化操作：
type UserInfo struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func anonymousStructDemo() {
	u1 := UserInfo{
		ID:   123,
		Name: "小米",
	}
	// 使用匿名结构体内嵌user并添加token
	b, err := json.Marshal(struct {
		*UserInfo
		Token string `json:"token"`
	}{
		&u1,
		"91je3l8a8d9f0g", // 添加的新字段，在不改变原有的基础之上
	})
	if err != nil {
		fmt.Println("json.Marshal failed ...")
	}
	fmt.Println(string(b))
}

// -----------------------------------------------------------

// 使用匿名结构体组合多个结构体
// 同理，也可以使用匿名结构体来组合多个结构体来序列化与反序列化数据：
// 组合结构体
type Comment struct {
	Content string
}

type Image struct {
	Title string `json:"title"`
	URL   string `json:"url"`
}

func anonymousStructDemo2() {
	c1 := Comment{Content: "你是一头猪吗?"}
	i1 := Image{Title: "小猪是猪吗?", URL: "www.是一头猪.com"}
	// 序列化
	b, err := json.Marshal(struct {
		*Comment
		*Image
	}{
		&c1,
		&i1,
	})
	if err != nil {
		log.Error("序列化失败...")
	}
	fmt.Println(string(b))
	// 反序列化
	jsonStr1 := `{"Content":"你真是一头猪","title":"论一头猪","url":"www.zhuzhu.com"}`
	var (
		c2 Comment
		i2 Image
	)

	json.Unmarshal([]byte(jsonStr1), &struct {
		*Comment
		*Image
	}{
		&c2,
		&i2,
	})
	fmt.Println(c2, i2)
}

// -----------------------------------------------------------
// 处理不同层级的json
// 如果json串没有固定的格式导致不好定义与其相对应的结构体时，
// 我们可以使用json.RawMessage原始字节数据保存下来。

type sendMsg struct {
	User string `json:"user"`
	Msg  string `json:msg`
}

func rawMessageDemo() {
	jsonStr1 := `{"content":{"user":"小猪","msg":"爱吃屎"},"age":99}`
	// 可以先把整个格式序列化 然后取对应的序列化即可
	var messageMarshal map[string]json.RawMessage
	json.Unmarshal([]byte(jsonStr1), &messageMarshal)
	fmt.Println("messageMarshal", messageMarshal)
	// 再取想要的数据
	var message sendMsg
	json.Unmarshal(messageMarshal["content"], &message)
	fmt.Println("message", message)
}
